// Package dao
// Created by GoLand
// @User: lenora
// @Date: 2024/8/16
// @Time: 14:58

package dao

import (
	"context"
	"gorm.io/gorm"
	"time"
)

type JobDAO interface {
	Preempt(ctx context.Context) (Job, error)
	Release(ctx context.Context, id uint64) error
	ResetNextTime(ctx context.Context, id uint64, nextTime time.Time) error
}

type GORMJobDAO struct {
	db *gorm.DB
}

func NewGORMJobDAO(db *gorm.DB) JobDAO {
	return &GORMJobDAO{
		db: db.Table(new(Job).TableName()),
	}
}

func (dao *GORMJobDAO) Release(ctx context.Context, id uint64) error {
	now := time.Now().UnixMilli()
	err := dao.db.WithContext(ctx).Where("id = ?", id).Updates(map[string]any{
		ut:       now,
		"status": 0,
	}).Error
	return err
}

func (dao *GORMJobDAO) ResetNextTime(ctx context.Context, id uint64, nextTime time.Time) error {
	now := time.Now().UnixMilli()
	return dao.db.WithContext(ctx).Where("id = ?", id).Updates(map[string]any{
		ut:          now,
		"next_time": nextTime.UnixMilli(),
	}).Error
}

func (dao *GORMJobDAO) Preempt(ctx context.Context) (Job, error) {
	for {
		var (
			j   Job
			now = time.Now().UnixMilli()
		)
		//查找waiting状态的任务
		err := dao.db.WithContext(ctx).
			Where("status = ? and next_time < ?", jobStatusWaiting, now).
			First(&j).
			Error
		if err != nil {
			return Job{}, err
		}
		//更新任务状态,version作为条件检索,也做同步更新操作,可++
		res := dao.db.WithContext(ctx).
			Where("id = ? and version = ?", j.Id, j.Version).
			Updates(map[string]any{
				"version": j.Version + 1,
				"status":  jobStatusRunning,
				"utime":   now,
			})
		if res.Error != nil {
			return Job{}, res.Error
		}
		if res.RowsAffected == 0 {
			//如果抢占失败,可再找新的任务
			continue
		}
		return j, nil
	}
}

type jobStatus uint8

const (
	jobStatusWaiting jobStatus = iota //当前空闲,等待被抢
	jobStatusRunning                  //正在执行,已经被抢
	jobStatusPaused                   //暂停,不需要再调度了
)

type Job struct {
	Id       uint64 `gorm:"primaryKey,autoIncrement"`
	Executor string //执行器
	Spec     string //定时任务表达式
	Status   int8   `gorm:"type:int(1);index:status_time"` //任务状态
	NextTime int64  `gorm:"index:status_time"`             //下次执行时间
	Version  int
	BaseTime
}

func (Job) TableName() string {
	return "job"
}
